/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
 */
package org.olap4j.mdx;

import org.olap4j.Axis;
import org.olap4j.type.Type;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;

/**
 * Parse tree model for an MDX SELECT statement.
 * 
 * @author jhyde
 * @since Jun 4, 2007
 */
public class SelectNode implements ParseTreeNode {
	private final ParseRegion region;
	private final List<ParseTreeNode> withList;
	private final List<AxisNode> axisList;
	private final AxisNode filterAxis;
	private final List<IdentifierNode> cellPropertyList;
	private ParseTreeNode from;

	/**
	 * Creates a SelectNode.
	 * 
	 * @param region
	 *            Region of source code from which this node was created
	 * @param withList
	 *            List of members and sets defined in this query using a <code>WITH</code> clause
	 * @param axisList
	 *            List of axes
	 * @param from
	 *            Contents of FROM clause (name of cube, or subquery)
	 * @param filterAxis
	 *            Filter axis
	 * @param cellPropertyList
	 *            List of properties
	 */
	public SelectNode(ParseRegion region, List<ParseTreeNode> withList, List<AxisNode> axisList, ParseTreeNode from,
			AxisNode filterAxis, List<IdentifierNode> cellPropertyList) {
		this.region = region;
		this.withList = withList;
		this.axisList = axisList;
		this.from = from;
		if (filterAxis == null) {
			filterAxis = new AxisNode(null, false, Axis.FILTER, Collections.<IdentifierNode> emptyList(), null);
		}
		if (filterAxis.getAxis() != Axis.FILTER) {
			throw new IllegalArgumentException("Filter axis must have type FILTER");
		}
		this.filterAxis = filterAxis;
		this.cellPropertyList = cellPropertyList;
	}

	/**
	 * Creates an empty SelectNode.
	 * 
	 * <p>
	 * The contents of the SelectNode, such as the axis list, can be populated after construction.
	 */
	public SelectNode() {
		this(null, new ArrayList<ParseTreeNode>(), new ArrayList<AxisNode>(), null, null,
				new ArrayList<IdentifierNode>());
	}

	public ParseRegion getRegion() {
		return region;
	}

	public <T> T accept(ParseTreeVisitor<T> visitor) {
		return visitor.visit(this);
	}

	public Type getType() {
		// not an expression, so has no type
		return null;
	}

	public String toString() {
		StringWriter sw = new StringWriter();
		ParseTreeWriter pw = new ParseTreeWriter(sw);
		unparse(pw);
		return sw.toString();
	}

	public void unparse(ParseTreeWriter writer) {
		final PrintWriter pw = writer.getPrintWriter();
		if (!withList.isEmpty()) {
			pw.println("WITH");
			for (ParseTreeNode with : withList) {
				with.unparse(writer);
				pw.println();
			}
		}
		pw.print("SELECT");
		int k = 0;
		for (AxisNode axis : axisList) {
			if (k++ > 0) {
				pw.println(",");
			} else {
				pw.println();
			}
			axis.unparse(writer);
		}
		pw.println();
		pw.print("FROM ");
		if (from instanceof SelectNode) {
			writer.indent();
			pw.println("(");
			from.unparse(writer);
			pw.print(")");
			writer.outdent();
		} else {
			from.unparse(writer);
		}
		if (filterAxis.getExpression() != null) {
			pw.println();
			pw.print("WHERE ");
			filterAxis.unparse(writer);
		}
		if (!cellPropertyList.isEmpty()) {
			pw.println();
			pw.print("CELL PROPERTIES ");
			k = 0;
			for (IdentifierNode cellProperty : cellPropertyList) {
				if (k++ > 0) {
					pw.print(", ");
				}
				cellProperty.unparse(writer);
			}
		}
	}

	/**
	 * Returns a list of calculated members and sets defined as the WITH clause of this SelectNode.
	 * 
	 * <p>
	 * For example, the WITH clause of query
	 * 
	 * <blockquote> <code>WITH MEMBER [Measures].[Foo] AS ' [Measures].[Unit Sales] * 2 '
	 *   SET [Customers].[Top] AS ' TopCount([Customers].Members, 10) '
	 * SELECT FROM [Sales]</code> </blockquote>
	 * 
	 * contains one {@link org.olap4j.mdx.WithMemberNode} and one {@link org.olap4j.mdx.WithSetNode}.
	 * 
	 * <p>
	 * The returned list is mutable.
	 * 
	 * @return list of calculated members and sets
	 */
	public List<ParseTreeNode> getWithList() {
		return withList;
	}

	/**
	 * Returns a list of axes in this SelectNode.
	 * 
	 * <p>
	 * The returned list is mutable.
	 * 
	 * @return list of axes
	 */
	public List<AxisNode> getAxisList() {
		return axisList;
	}

	/**
	 * Returns the filter axis defined by the WHERE clause of this SelectNode.
	 * 
	 * <p>
	 * Never returns {@code null}. If there is no WHERE clause, returns an AxisNode for which
	 * {@link org.olap4j.mdx.AxisNode#getExpression()} returns null.
	 * 
	 * <p>
	 * You can modify the filter expression by calling {@link org.olap4j.mdx.AxisNode#getExpression()} on the filter
	 * AxisNode; {@code null} means that there is no filter axis.
	 * 
	 * @return filter axis
	 */
	public AxisNode getFilterAxis() {
		return filterAxis;
	}

	/**
	 * Returns the node representing the FROM clause of this SELECT statement. The node is typically an
	 * {@link IdentifierNode}, a {@link CubeNode} or a {@link SelectNode}.
	 * 
	 * @return FROM clause
	 */
	public ParseTreeNode getFrom() {
		return from;
	}

	/**
	 * Sets the FROM clause of this SELECT statement.
	 * 
	 * <p>
	 * <code>fromNode</code> should typically by an {@link org.olap4j.mdx.IdentifierNode} containing the cube name, or a
	 * {@link org.olap4j.mdx.CubeNode} referencing an explicit {@link org.olap4j.metadata.Cube} object.
	 * 
	 * @param from
	 *            FROM clause
	 */
	public void setFrom(ParseTreeNode from) {
		this.from = from;
	}

	/**
	 * Returns a list of cell properties in this SelectNode.
	 * 
	 * <p>
	 * The returned list is mutable.
	 * 
	 * @return list of cell properties
	 */
	public List<IdentifierNode> getCellPropertyList() {
		return cellPropertyList;
	}

	public SelectNode deepCopy() {
		return new SelectNode(this.region, MdxUtil.deepCopyList(withList), MdxUtil.deepCopyList(axisList),
				this.from != null ? this.from.deepCopy() : null, this.filterAxis.deepCopy(),
				MdxUtil.deepCopyList(cellPropertyList));
	}
}

// End SelectNode.java
